﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

namespace XNAGame
{
    public class Player
    {
        //I actually did split up public and private, just to keep things simple outside of this class file
        public Camera cam;
        public Vector3 vPosition;
        public float fSpeed = 0.5f;
        public float fSensitivity = 1;
        public Vector3 vAim;
        public Vector3 vAimBlock;
        public int nNoclipSpam = 0;
        public VertexPositionTexture[] rFaceMarker;
        public Vector3 vPlayerVel;
        public bool bUnderwater;
        public bool bDebug = true;
        public int nHealth = 9;

        private Game1 game;
        private bool bNoClip = false;
        private bool bJumping;

        private int nDebugSpam = 0;

        //Constructor
        public Player(Game1 gameIn)
        {
            cam = new Camera(gameIn, new Vector3(0, 64, 0), new Vector3(0, 0, 0));
            game = gameIn;
            vPosition = new Vector3((int)(game.vDimensions.X / 2), game.rHeightMap[(int)(game.vDimensions.X / 2), (int)(game.vDimensions.Z / 2)] + 1.5f, (int)(game.vDimensions.Z / 2));
        }

        //Main update method, is called from Game1.cs's update method
        public void Update(MouseState mouseState, KeyboardState keyState)
        {
            Vector3 vTestVector = new Vector3(0, 0, 0);
            Vector3 vTestBlock;
            Vector3 vForward;

            //Noclip
            nNoclipSpam = (nNoclipSpam > 0 ? nNoclipSpam - 1 : nNoclipSpam);
            if (nNoclipSpam == 0 && keyState.IsKeyDown(Keys.V))
            {
                nNoclipSpam = 15;
                bNoClip = !bNoClip;
            }
            //Debug overlay
            nDebugSpam = (nDebugSpam > 0 ? nDebugSpam - 1 : nDebugSpam);
            if (nDebugSpam == 0 && keyState.IsKeyDown(Keys.F1))
            {
                nDebugSpam = 15;
                bDebug = !bDebug;
            }

            //If not noclipped, execute standard movement code
            if (!bNoClip)
            {
                //Set speed
                fSpeed = 0.1f;

                //If space, then either jump on land or float up in water
                if (keyState.IsKeyDown(Keys.Space))
                {
                    if (bUnderwater)
                    {
                        vPlayerVel.Y = 0.04f;
                    }
                    else if (!bJumping)
                    {
                        vPlayerVel.Y = 0.11f;
                        bJumping = true;
                    }
                }

                //Keep jumping to false if underwater
                if (bUnderwater)
                {
                    bJumping = false;
                }

                //Get the forward vector (so that it doesn't need to keep accessing it), set the Y component to 0 because y movement is limited on the ground
                vForward = Vector3.Normalize(new Vector3(cam.Forward.X, 0, cam.Forward.Z));

                //Set the projected velocities
                vPlayerVel.Z = MathHelper.Clamp(vPlayerVel.Z + (keyState.IsKeyDown(Keys.W) ? (bJumping ? fSpeed / 24 : fSpeed) : 0) + (keyState.IsKeyDown(Keys.S) ? -(bJumping ? fSpeed / 24 : fSpeed) : 0) - Math.Sign(vPlayerVel.Z) / (100f + (bJumping ? 900f : 0f)), -fSpeed, fSpeed);
                vPlayerVel.Z = (Math.Abs(vPlayerVel.Z) <= 0.0025 ? 0 : vPlayerVel.Z);

                vPlayerVel.Y = MathHelper.Clamp(vPlayerVel.Y - (bUnderwater ? 0.0005f : 0.005f), (bUnderwater ? -0.025f : -0.35f), 0.11f);

                vPlayerVel.X = MathHelper.Clamp(vPlayerVel.X + (keyState.IsKeyDown(Keys.A) ? (bJumping ? fSpeed / 24 : fSpeed / 2) : 0) + (keyState.IsKeyDown(Keys.D) ? (bJumping ? -fSpeed / 24 : -fSpeed / 2) : 0) - Math.Sign(vPlayerVel.X) / (100f + (bJumping ? 900f : 0f)), -fSpeed / 2f, fSpeed / 2f);
                vPlayerVel.X = (Math.Abs(vPlayerVel.X) <= 0.0025 ? 0 : vPlayerVel.X);

                //Create a test vector of what would happen if this velocity was applied
                vTestVector = vPosition;
                vTestVector -= new Vector3((vForward * vPlayerVel.Z).X, 0, (vForward * vPlayerVel.Z).Z);
                vTestVector -= new Vector3((cam.Right * vPlayerVel.X).X, 0, (cam.Right * vPlayerVel.X).Z);
                vTestVector.Y = vPosition.Y;
                vTestVector.Y += vPlayerVel.Y;
                vTestBlock = new Vector3((int)Math.Floor(vTestVector.X), (int)Math.Floor(vTestVector.Y), (int)Math.Floor(vTestVector.Z));

                //Check the test vector's collision on a per axis basis
                if (vTestBlock.X > 0 && vTestBlock.X < game.vDimensions.X - 1 && vTestBlock.Z > 0 && vTestBlock.Z < game.vDimensions.Z - 1 && vTestBlock.Y > 0 && vTestBlock.Y < game.vDimensions.Y)
                {
                    //Check the X axis at foot level
                    if (game.rBlockMap[(int)vTestBlock.X + 1, (int)vTestBlock.Y, (int)vTestBlock.Z].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X + 1, (int)vTestBlock.Y, (int)vTestBlock.Z]))
                        {
                            vTestVector.X = ((int)vPosition.X) + 0.74f;
                        }
                    }
                    if (game.rBlockMap[(int)vTestBlock.X - 1, (int)vTestBlock.Y, (int)vTestBlock.Z].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X - 1, (int)vTestBlock.Y, (int)vTestBlock.Z]))
                        {
                            vTestVector.X = ((int)vPosition.X) + 0.26f;
                        }
                    }
                    //Check the X axis at head level
                    if (game.rBlockMap[(int)vTestBlock.X + 1, (int)vTestBlock.Y + 1, (int)vTestBlock.Z].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X + 1, (int)vTestBlock.Y + 1, (int)vTestBlock.Z]))
                        {
                            vTestVector.X = ((int)vPosition.X) + 0.74f;
                        }
                    }
                    if (game.rBlockMap[(int)vTestBlock.X - 1, (int)vTestBlock.Y + 1, (int)vTestBlock.Z].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X - 1, (int)vTestBlock.Y + 1, (int)vTestBlock.Z]))
                        {
                            vTestVector.X = ((int)vPosition.X) + 0.26f;
                        }
                    }

                    //Check the Y axis, but also check the surrounding blocks so that you can hang off of edges of blocks
                    for (int x = (int)vTestBlock.X - 1; x <= (int)vTestBlock.X + 1; x++)
                    {
                        for (int z = (int)vTestBlock.Z - 1; z <= (int)vTestBlock.Z + 1; z++)
                        {
                            //Check up
                            if (game.rBlockMap[x, (int)vTestBlock.Y + 2, z].Solid)
                            {
                                BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                                if (playerBox.Intersects(game.rBoundingBoxes[x, (int)vTestBlock.Y + 2, z]))
                                {
                                    vTestVector.Y = (int)vTestBlock.Y + 0.82f;
                                    vPlayerVel.Y = 0;
                                }
                            }

                            //Check down
                            if (game.rBlockMap[x, (int)vTestBlock.Y - 1, z].Solid)
                            {
                                BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                                if (playerBox.Intersects(game.rBoundingBoxes[x, (int)vTestBlock.Y - 1, z]))
                                {
                                    vTestVector.Y = (int)vTestBlock.Y + 0.5f;
                                    vPlayerVel.Y = 0;
                                    bJumping = false;
                                }
                            }
                        }
                    }

                    //Check the Z axis at foot level
                    if (game.rBlockMap[(int)vTestBlock.X, (int)vTestBlock.Y, (int)vTestBlock.Z + 1].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X, (int)vTestBlock.Y, (int)vTestBlock.Z + 1]))
                        {
                            vTestVector.Z = ((int)vPosition.Z) + 0.74f;
                        }
                    }
                    if (game.rBlockMap[(int)vTestBlock.X, (int)vTestBlock.Y, (int)vTestBlock.Z - 1].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X, (int)vTestBlock.Y, (int)vTestBlock.Z - 1]))
                        {
                            vTestVector.Z = ((int)vPosition.Z) + 0.26f;
                        }
                    }
                    //Check the Z axis at head level
                    if (game.rBlockMap[(int)vTestBlock.X, (int)vTestBlock.Y + 1, (int)vTestBlock.Z + 1].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X, (int)vTestBlock.Y + 1, (int)vTestBlock.Z + 1]))
                        {
                            vTestVector.Z = ((int)vPosition.Z) + 0.74f;
                        }
                    }
                    if (game.rBlockMap[(int)vTestBlock.X, (int)vTestBlock.Y + 1, (int)vTestBlock.Z - 1].Solid)
                    {
                        BoundingBox playerBox = new BoundingBox(vTestVector + new Vector3(-0.25f, -0.5f, -0.25f), vTestVector + new Vector3(0.25f, 1.167f, 0.25f));
                        if (playerBox.Intersects(game.rBoundingBoxes[(int)vTestBlock.X, (int)vTestBlock.Y + 1, (int)vTestBlock.Z - 1]))
                        {
                            vTestVector.Z = ((int)vPosition.Z) + 0.26f;
                        }
                    }

                    vPosition = vTestVector;
                }

            }
            else //If noclipped
            {
                fSpeed = 0.5f + (keyState.IsKeyDown(Keys.LeftShift) ? 0.5f : (keyState.IsKeyDown(Keys.LeftControl) ? -0.4f : 0)); //Set speed up, set position based off of all this shit:
                Vector3 vNoClipTestVector = vPosition - ((Vector3.Up * fSpeed * (keyState.IsKeyDown(Keys.Space) ? -1 : 0)) + (cam.Forward * fSpeed * ((keyState.IsKeyDown(Keys.W) ? 1 : 0) - (keyState.IsKeyDown(Keys.S) ? 1 : 0))) + (cam.Right * fSpeed / 2f * ((keyState.IsKeyDown(Keys.A) ? 1 : 0) - (keyState.IsKeyDown(Keys.D) ? 1 : 0))));
                if (vNoClipTestVector.X > 0 && vNoClipTestVector.X < game.vDimensions.X && vNoClipTestVector.Y > 0 && vNoClipTestVector.Y < (game.vDimensions.Y - 2) && vNoClipTestVector.Z > 0 && vNoClipTestVector.Z < game.vDimensions.Z)
                {
                    vPosition = vNoClipTestVector;
                }
                

            }

            //Re-initialize aim and aimblock vectors
            vAim = new Vector3();
            vAimBlock = new Vector3();

            //Check along a path stemming from the camera's forward if there is a collision
            for (float i = 0; i <= 6; i += 0.01f)
            {
                if (i < 5.5f)
                {
                    vAim = cam.Position - cam.Forward * i;
                    try
                    {
                        if (game.rBlockMap[(int)Math.Floor(vAim.X), (int)Math.Floor(vAim.Y), (int)Math.Floor(vAim.Z)].AimSolid)
                        {
                            break; //If there is, break the loop with the current aim vector
                        }
                    }
                    catch
                    {
                        vAim = new Vector3(-10, -10, -10);
                        break;
                    }
                }
                else
                {
                    vAim = new Vector3(-10, -10, -10);
                }
            } //Otherwise set it to be an empty vector

            if (vAim != new Vector3(-10, -10, -10))
            {
                vAimBlock = new Vector3((int)Math.Floor(vAim.X), (int)Math.Floor(vAim.Y), (int)Math.Floor(vAim.Z)); //Get the aim block based off of that aim vector
            }


            cam.Position = vPosition + new Vector3(0, 1.167f, 0); //Set camera position to be player position plus 7/6ths on the z axis
            cam.Rotate( //Rotate the camera based off of mouse position, set mouse position to be screen center
                MathHelper.ToRadians((mouseState.Y - game.device.DisplayMode.Height / 2) * fSensitivity * 0.1f),
                MathHelper.ToRadians((mouseState.X - game.device.DisplayMode.Width / 2) * fSensitivity * 0.1f),
                0.0f
                );
            Mouse.SetPosition(game.device.DisplayMode.Width / 2, game.device.DisplayMode.Height / 2);

            //Check if underwater
            try { bUnderwater = (game.rBlockMap[(int)Math.Floor(cam.Position.X), (int)Math.Floor(cam.Position.Y), (int)Math.Floor(cam.Position.Z)] == game.rBlocks[8]); }
            catch { }

            CreateFaceMarker(); //Create the face marker's vertices - I need to redo this method
        }

        public void CreateFaceMarker()
        {
            //Create the face marker's vertices - I need to redo this method

            Vector3 vDifference = GetFacingBlock() - vAimBlock; //Get a difference vector to see where the facing block is local to the aim block
            rFaceMarker = new VertexPositionTexture[6]; //Initialize the array of vectors - we only need 6 to make a square

            //Check the differences and draw the face marker accordingly
            if (vDifference.X == -1)
            {
                rFaceMarker[0] = new VertexPositionTexture(new Vector3(vAimBlock.X - 0.01f, vAimBlock.Y + 1, vAimBlock.Z), new Vector2(0.5f, 0));
                rFaceMarker[1] = new VertexPositionTexture(new Vector3(vAimBlock.X - 0.01f, vAimBlock.Y + 1, vAimBlock.Z + 1), new Vector2(1, 0));
                rFaceMarker[2] = new VertexPositionTexture(new Vector3(vAimBlock.X - 0.01f, vAimBlock.Y, vAimBlock.Z), new Vector2(0.5f, 1));
                rFaceMarker[3] = new VertexPositionTexture(new Vector3(vAimBlock.X - 0.01f, vAimBlock.Y + 1, vAimBlock.Z + 1), new Vector2(1, 0));
                rFaceMarker[4] = new VertexPositionTexture(new Vector3(vAimBlock.X - 0.01f, vAimBlock.Y, vAimBlock.Z + 1), new Vector2(1, 1));
                rFaceMarker[5] = new VertexPositionTexture(new Vector3(vAimBlock.X - 0.01f, vAimBlock.Y, vAimBlock.Z), new Vector2(0.5f, 1));
            }
            else if (vDifference.X == 1)
            {
                rFaceMarker[0] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1.01f, vAimBlock.Y + 1, vAimBlock.Z + 1), new Vector2(0.5f, 0));
                rFaceMarker[1] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1.01f, vAimBlock.Y + 1, vAimBlock.Z), new Vector2(1, 0));
                rFaceMarker[2] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1.01f, vAimBlock.Y, vAimBlock.Z + 1), new Vector2(0.5f, 1));
                rFaceMarker[3] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1.01f, vAimBlock.Y + 1, vAimBlock.Z), new Vector2(1, 0));
                rFaceMarker[4] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1.01f, vAimBlock.Y, vAimBlock.Z), new Vector2(1, 1));
                rFaceMarker[5] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1.01f, vAimBlock.Y, vAimBlock.Z + 1), new Vector2(0.5f, 1));
            }
            else if (vDifference.Y == -1)
            {
                rFaceMarker[0] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y - 0.01f, vAimBlock.Z + 1), new Vector2(0.5f, 0));
                rFaceMarker[1] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y - 0.01f, vAimBlock.Z), new Vector2(1, 0));
                rFaceMarker[2] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y - 0.01f, vAimBlock.Z + 1), new Vector2(0.5f, 1));
                rFaceMarker[3] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y - 0.01f, vAimBlock.Z), new Vector2(1, 0));
                rFaceMarker[4] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y - 0.01f, vAimBlock.Z), new Vector2(1, 1));
                rFaceMarker[5] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y - 0.01f, vAimBlock.Z + 1), new Vector2(0.5f, 1));
            }
            else if (vDifference.Y == 1)
            {
                rFaceMarker[0] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y + 1.01f, vAimBlock.Z), new Vector2(0.5f, 0));
                rFaceMarker[1] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y + 1.01f, vAimBlock.Z + 1), new Vector2(1, 0));
                rFaceMarker[2] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y + 1.01f, vAimBlock.Z), new Vector2(0.5f, 1));
                rFaceMarker[3] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y + 1.01f, vAimBlock.Z + 1), new Vector2(1, 0));
                rFaceMarker[4] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y + 1.01f, vAimBlock.Z + 1), new Vector2(1, 1));
                rFaceMarker[5] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y + 1.01f, vAimBlock.Z), new Vector2(0.5f, 1));
            }
            else if (vDifference.Z == 1)
            {
                rFaceMarker[0] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y + 1, vAimBlock.Z + 1.01f), new Vector2(0.5f, 0));
                rFaceMarker[1] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y + 1, vAimBlock.Z + 1.01f), new Vector2(1, 0));
                rFaceMarker[2] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y, vAimBlock.Z + 1.01f), new Vector2(0.5f, 1));
                rFaceMarker[3] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y + 1, vAimBlock.Z + 1.01f), new Vector2(1, 0));
                rFaceMarker[4] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y, vAimBlock.Z + 1.01f), new Vector2(1, 1));
                rFaceMarker[5] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y, vAimBlock.Z + 1.01f), new Vector2(0.5f, 1));
            }
            else if (vDifference.Z == -1)
            {
                rFaceMarker[0] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y + 1, vAimBlock.Z - 0.01f), new Vector2(0.5f, 0));
                rFaceMarker[1] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y + 1, vAimBlock.Z - 0.01f), new Vector2(1, 0));
                rFaceMarker[2] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y, vAimBlock.Z - 0.01f), new Vector2(0.5f, 1));
                rFaceMarker[3] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y + 1, vAimBlock.Z - 0.01f), new Vector2(1, 0));
                rFaceMarker[4] = new VertexPositionTexture(new Vector3(vAimBlock.X, vAimBlock.Y, vAimBlock.Z - 0.01f), new Vector2(1, 1));
                rFaceMarker[5] = new VertexPositionTexture(new Vector3(vAimBlock.X + 1, vAimBlock.Y, vAimBlock.Z - 0.01f), new Vector2(0.5f, 1));
            }
        }

        public Vector3 GetFacingBlock()
        {
            //Initialize vectors and a float which will be used to sort out which axis is most different
            Vector3 vDifference = new Vector3();
            Vector3 vFacingBlock = new Vector3();
            float tempcomp = 0;
            vDifference = vAim - vAimBlock - new Vector3(0.5f, 0.5f, 0.5f); //Get aim vec local to aim block

            //This method works by getting on which axis the local aim position is greatest - i.e. which face the cursor is on
            if (Math.Abs(vDifference.X) > Math.Abs(tempcomp))
            {
                tempcomp = vDifference.X;
                vFacingBlock = new Vector3(vAimBlock.X + Math.Sign(vDifference.X), vAimBlock.Y, vAimBlock.Z);
            }
            if (Math.Abs(vDifference.Y) > Math.Abs(tempcomp))
            {
                tempcomp = vDifference.Y;
                vFacingBlock = new Vector3(vAimBlock.X, vAimBlock.Y + Math.Sign(vDifference.Y), vAimBlock.Z);
            }
            if (Math.Abs(vDifference.Z) > Math.Abs(tempcomp))
            {
                tempcomp = vDifference.Z;
                vFacingBlock = new Vector3(vAimBlock.X, vAimBlock.Y, vAimBlock.Z + Math.Sign(vDifference.Z));
            }

            return vFacingBlock;
        }
    }
}
